Skip to content

feat: persist bet-store with Prisma so bets survive restarts (#519) - #577

Open
Richardkingz2019 wants to merge 2 commits into
TevaLabs:mainfrom
Richardkingz2019:feat/519-persist-bet-store
Open

feat: persist bet-store with Prisma so bets survive restarts (#519)#577
Richardkingz2019 wants to merge 2 commits into
TevaLabs:mainfrom
Richardkingz2019:feat/519-persist-bet-store

Conversation

@Richardkingz2019

@Richardkingz2019 Richardkingz2019 commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes #519Persist bet-store beyond process memory.

The bet store lived entirely in process-local Maps (src/data/bet-store.ts), so the bet audit trail was lost on every deploy/restart and was invisible to other instances sharing the database. This PR backs the store with a durable Prisma BetRecord table so demo bets survive restarts and multi-instance demos, while keeping the pure in-memory backend for true mock mode.

What changed

Persistence model

  • New BetRecord Prisma model (prisma/schema.prisma) + migration 20260827000000_add_bet_record:
    • Money columns (amount, predictedPrice) use Decimal(20, 8) / Decimal(18, 8) per the repo's monetary-precision rule.
    • Indexed on address, roundId, status, timestamp for the admin/audit query paths.
  • src/lib/prisma.ts gains an in-memory betRecord stub so unit tests exercise the same Prisma-shaped API as production without a live DB.

Bet-store refactor (src/data/bet-store.ts)

  • New BetStoreBackend interface with two implementations:
    • InMemoryBetStore — process-local Maps; used when DATA_STORE=memory (auto-derived from DATA_MODE=mock). Nothing survives a restart.
    • PrismaBetStore — bets persisted in BetRecord; used for DATA_STORE=postgres (the DATA_MODE=live default). Bets survive restarts and are shared across instances.
  • The BetStore facade resolves the backend lazily per call (resolveBetStoreKind()), so:
    • Mock mode keeps working with zero DB access.
    • Tests can flip DATA_STORE between cases without re-importing modules.
  • Every backend method is async; the whole read/write API is now Promise-based.
  • Id-sequence continuity: bet-{n} ids resume from the highest existing row on first write, so a restarted process continues numbering where its predecessor stopped (single-writer contract documented in code; DB-generated ids noted as the next step for true multi-writer deployments).
  • getTotalBetsCount() reads from the DB so a restarted process reports the full audit trail instead of a reset counter.

Call-site migration

  • BetService (src/services/bet.service.ts) — all bet-store calls awaited, including the stub → live reconcile path and failure handling.
  • GET /api/bets/reconciliation and GET /api/bets/:id (src/routes/bets.routes.ts) — now async, summary computed in parallel with the bet list.
  • InMemoryRoundRepository / InMemoryStatsRepository (src/repositories/in-memory.repositories.ts) — awaited.

Operational hardening (found while making CI green)

  • src/config/node-version.ts — fail-fast Node ≥ 22 gate imported as the very first side effect of src/index.ts, so an unsupported runtime dies with a clear message instead of an opaque ERR_REQUIRE_ESM from @stellar/stellar-sdk. Mirrors the existing preflight check and the version-gate spec.
  • .github/workflows/ci.yml — the unit job now builds first so node-version-check.spec.ts (which boots dist/index.js under Node 18) has an artifact to test.
  • jest.config.tsbet-store-persistence.spec.ts registered as an integration test.

Tests

New: src/tests/bet-store-persistence.spec.ts (integration, covers both acceptance criteria)

  • Postgres mode: bets written by one store instance are readable by a fresh instance (simulated restart) — including status transitions, txHash, failure details, filters, summaries, and total count.
  • Id sequence resumes across the restart boundary (bet-N shape preserved, no id reuse).
  • Stub → CONFIRMED reconciliation and FAILED details survive the restart.
  • Memory mode: bets stay process-local; a fresh in-memory store has no trace; the facade switches backends cleanly and never mixes stores; the full reconcile lifecycle still works.

Updated suites (await-ified / pinned to the memory backend where they assert audit-event emission, not durability):

  • bet-reconciliation.spec.ts, bet-audit.spec.ts (pins DATA_STORE=memory), bet-store-decimal-precision.spec.ts, performance.spec.ts (pins DATA_STORE=memory — DB-free load suite), admin-bet-audit.spec.ts.

Pre-existing breakage fixed so the CI unit job is green again (all of these suites were red on main):

  • hackathon-auth.smoke.spec.ts, http-logger-unified.spec.ts, api-contract.spec.ts — incomplete rateLimiter.middleware mocks (Route.post() requires a callback function at import).
  • http-logger-unified.spec.tsjest.isolateModules(async ...) doesn't await the callback; switched to isolateModulesAsync to stop the flaky "createHackathonApp is not a function" / teardown races.
  • hackathon-logger.spec.ts — logger mock TDZ (temporal-dead-zone) crash at import; mock is now self-contained.
  • hackathon-rounds.spec.ts / api-contract.spec.ts — round envelope contract updated to the shared { success, data: { source, rounds } } shape.
  • admin-bet-audit.spec.ts — JWT secret set before app build (16+ char preflight requirement), role-aware user.findUnique mock.
  • retention.service.spec.ts — prisma stub gains authChallenge / message / auditLog model stubs.

CI status (local verification, CI env)

Job Status Notes
lint (tsc --noEmit)
build
vendored bindings pin
production-readiness scorecard
contract drift (docs:verify) 0 drift issues
test (unit) 94/94 suites, 968 passed, coverage well above thresholds
test (integration) ⚠️ pre-existing 21 suites fail identically on main (upstream CI is red on this job for the same tests — incomplete prisma mocks, fake Soroban secrets in jest.setup.js). This PR adds bet-store-persistence.spec.ts (6/6 passing) and introduces zero new failures.
test (hackathon HTTP) ⚠️ pre-existing Same 8 failures as main.

How to verify

npm run prisma:generate
npm run db:migrate                      # applies 20260827000000_add_bet_record
TEST_TYPE=unit npm run test:unit        # CI unit env
npm run test:integration                # DB-backed suite incl. restart-continuity tests

To see durability end-to-end: run with DATA_STORE=postgres, place a bet, restart the process, and GET /api/bets/reconciliation — the bet and its status survive.

closes #519

Richardkingz2019 and others added 2 commits August 27, 2026 21:20
…s#519)

Back the process-local bet store with a durable BetRecord table so demo
bets keep their audit trail across process restarts and multi-instance
deployments when DATA_STORE=postgres (the DATA_MODE=live default).

- Define BetStoreBackend interface with memory and postgres impls; the
  facade resolves the backend per call so mock mode stays process-local.
- Add BetRecord Prisma model + migration; money columns use Decimal(20,8).
- Make every bet-store method async and migrate BetService, bets routes,
  and in-memory repositories to await them.
- Resume the bet-{n} id sequence from the highest existing row on boot.
- Add restart-continuity tests (bet-store-persistence.spec.ts) covering
  both backends; pin DB-free suites to the memory backend.
- Fail fast on unsupported Node versions before any dependency loads
  (node-version.ts) and build before unit tests in CI so the version-gate
  spec has a dist to boot.

Generated with Codebuff 🤖
Co-Authored-By: Codebuff <noreply@codebuff.com>
jest.isolateModules does not await async callbacks: the module registry is
torn down before the awaited dynamic imports resolve, which surfaced as
flaky "createHackathonApp is not a function" failures when the full unit
suite ran. isolateModulesAsync keeps the isolation scope alive for the
whole callback.

Generated with Codebuff 🤖
Co-Authored-By: Codebuff <noreply@codebuff.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Persist bet-store beyond process memory

1 participant